/*
 * @Author: '浪川' '1214391613@qq.com'
 * @Date: 2025-07-14 16:09:40
 * @LastEditors: '浪川' '1214391613@qq.com'
 * @LastEditTime: 2025-08-06 15:38:52
 * @FilePath: \passiflora-edulis-sims\src\utils\generate_double_puzzle.ts
 * @Description:
 *
 * Copyright (c) 2025 by '1214391613@qq.com', All Rights Reserved.
 */
import type { IStroke, IStrokePaint } from 'leafer-ui'
import { Group, Text } from 'leafer-ui'
import type { ICanvas, IGrid, IText } from 'src/interfaces/IBook'
import {
  create_boxtext,
  leafer_position,
  x_center_position,
} from './LeaferUtil'
import { BasePuzzleUtil } from './BasePuzzleUtil'

type Position = [number, number] // [wordIndex, charIndex]
type LetterPositions = Record<string, Position[]>
/**
 * @description: 绘制拼图
 * @param {*} answers: 输入答案
 * @return {*}:
 */
export function shuffle_array<T>(array: T[]) {
  const input_text = array.join('') // 拼接字符串
  let outpu_text = array.join('')
  let arr: T[] = []
  do {
    arr = array.sort(() => Math.random() - 0.5)
    outpu_text = arr.join('')
  } while (input_text === outpu_text)
  return arr
}

function get_random_position_index(
  positions: LetterPositions,
  letter: string,
  defaultValue: Position = [-1, -1]
): Position {
  const letterPositions = positions[letter.toLowerCase()]

  if (!letterPositions || letterPositions.length === 0) {
    return defaultValue
  }

  const randomIndex = Math.floor(Math.random() * letterPositions.length)
  return letterPositions.splice(randomIndex, 1)[0] as [number, number]
}

/**
 * @description: 判断两个数组是否相同
 * @param {T} arr1: 数组1
 * @param {T} arr2: 数组2
 * @return {*}: 返回两个数组是否相同
 */
function compare_array<T>(arr1: T[], arr2: T[]): boolean {
  // 判断长度是否一致
  if (arr1.length !== arr2.length) return false

  // 循环判断值是否一致
  for (let i = 0; i < arr1.length, i < arr2.length; i++) {
    if (arr1[i] !== arr2[i]) return false
  }

  return true
}

/**
 *  双重拼图的类
 * TOOD：当设置页面留白的时候 Grid的内部应该会出现留白 但是 并不计算进有效的width中导致无法居中对齐
 */
export class DoublePuzzle extends BasePuzzleUtil {
  message_text: string[]
  reveal_list: string[]

  max_wordlen: number = 0 // word 列表中 最长的单词长度
  max_grid_num: number = 0 // 一行最多有多少个方框
  recode_letter_index: Record<string, [number, number][]> // 记录每个单词的出现位置
  // 记录 words 中出现的每一个单词，防止 `meassage`中的单词字母获取不到word单词字母的文字
  arrival_letters: Set<string> = new Set()

  message_grid_style: IGrid

  reveal_grid_style: IGrid
  reveal_letter_location: 'inside' | 'outside'

  /**
   * @description: 创建双重拼图
   * @param {string} message_text: 信息字符串
   * @param {string} reveal_list: 提示单词
   * @param {number} max_wordlen: 拼图单词的最长长度
   * @param {IGrid} message_grid_style: box框的样式
   * @param {IGrid} reveal_grid_style: box框的样式
   * @param {*} reveal_letter_location: message的单词的提示下标的所处位置
   * @return {*}:
   */
  constructor(
    canvas: ICanvas,
    title_model: IText,
    instruction_model: IText,
    word_list: string[],
    words_grid_style: IGrid,

    message_text: string[],
    reveal_list: string[],
    max_wordlen: number,
    message_grid_style: IGrid,
    reveal_grid_style: IGrid,
    reveal_letter_location: 'inside' | 'outside' = 'inside'
  ) {
    super(canvas, title_model, instruction_model, word_list, words_grid_style)
    this.message_text = message_text
    this.reveal_list = reveal_list
    this.max_wordlen = max_wordlen

    this.recode_letter_index = this.record_letter_positions(this.word_list)

    this.words_grid_style = words_grid_style
    this.message_grid_style = message_grid_style
    this.reveal_grid_style = reveal_grid_style

    this.reveal_letter_location = reveal_letter_location

    this.max_grid_num =
      Math.floor(this.canvas.width / words_grid_style.width) - 2
  }

  generate_puzzle = (): void => {
    // 清除画布的元素
    this.answer_frame.clear()
    this.topic_frame.clear()

    // 生成 标题和副标题
    this.dram_title(this.title_model, this.instruction_model)
    // 生成 words 区域
    this.dram_words()
    // 生成 message区域
    this.draw_message(
      this.ans_words_group.boxBounds.height +
        this.title_group.boxBounds.height +
        2 * 100
    )

    // 将 title groud 添加进 frame
    /**
     * TOOD 需要解决 title_group 不居中的问题
     */
    this.title_group.x = x_center_position(
      this.title_group.boxBounds.width,
      this.topic_frame.width as number
    )
    this.title_group.y = this.canvas.height * 0.03
    this.answer_frame.add(this.title_group)
    this.topic_frame.add(this.title_group.clone())

    // 设置 word and  topic groud 的坐标位置
    const words_grid_ceterX = x_center_position(
      this.ans_words_group.boxBounds.width,
      this.answer_frame.width as number
    )
    this.ans_words_group.x = words_grid_ceterX
    this.topic_words_group.x = words_grid_ceterX

    this.ans_words_group.y =
      this.title_group.boxBounds.height +
      this.canvas.height * 0.06 +
      this.words_grid_style.offet_y
    this.topic_words_group.y =
      this.title_group.boxBounds.height +
      this.canvas.height * 0.06 +
      this.words_grid_style.offet_y
    // 将组添加进 fram
    this.answer_frame.add(this.ans_words_group)
    this.topic_frame.add(this.topic_words_group)
  }

  dram_words = () => {
    // 遍历 answer
    let row_num = 0
    for (const row of this.word_list) {
      let col_num = 0

      // 打乱单词
      const shuffle_word = shuffle_array<string>(row.split(''))

      // 计算当前单词 的坐标
      const [eq_x, eq_y] = leafer_position({
        x: col_num,
        y: row_num,
        width: this.words_grid_style.width,
        height: this.words_grid_style.width,
        stroke: this.words_grid_style.stroke as IStrokePaint,
        white_border: this.white_border,
        x_offet: this.words_grid_style.offet_x,
        y_offet: this.words_grid_style.offet_y,
        x_interval: this.words_grid_style.x_interval,
        y_interval: this.words_grid_style.y_interval,
      })

      // 计算 考试题 的方框长度
      const eq_box_len = (this.words_grid_style.width * this.max_wordlen) / 2
      // 绘制 examination questions 的方框
      const eq_boxtext = create_boxtext({
        x: eq_x,
        y: eq_y,
        width: eq_box_len,
        height: this.words_grid_style.height,
        text: shuffle_word.join('').toUpperCase(),
        text_style: this.words_grid_style.text as IText,
        stroke: this.words_grid_style.stroke as IStroke,
      })
      // 将绘制的 eq 方框添加进 画布
      this.ans_words_group.add(eq_boxtext)
      this.topic_words_group.add(eq_boxtext.clone())

      for (const ans_w of row) {
        // 创建 答案box
        // 获取实际渲染位置
        if (ans_w == ' ') {
          col_num++
          continue
        }
        /**
         * 公式为：box 的宽度 * 当前的列 + 100的两边留白 + box 的描边宽度 * 当前列 + 前一个Box的位置 + 500的间隔
         */
        const [real_x, real_y] = leafer_position({
          x: col_num,
          y: row_num,
          width: this.words_grid_style.width,
          height: this.words_grid_style.width,
          stroke: this.words_grid_style.stroke as IStrokePaint,
          white_border: this.white_border,
          x_offet: this.words_grid_style.width + eq_box_len,
          y_offet: this.words_grid_style.offet_y,
          x_interval: this.words_grid_style.x_interval,
          y_interval: this.words_grid_style.y_interval,
        })
        // 用三元表达式 判断 当前字母 是否为 提示字母字母
        this.topic_words_group.add(
          create_boxtext({
            x: real_x,
            y: real_y,
            width: this.reveal_grid_style.height,
            height: this.reveal_grid_style.height,
            text: this.reveal_list.includes(ans_w.toUpperCase()) ? ans_w : ' ',
            text_style: this.reveal_grid_style.text as IText,
            stroke: this.reveal_grid_style.stroke as IStroke,
          })
        )
        this.ans_words_group.add(
          create_boxtext({
            x: real_x,
            y: real_y,
            width: this.reveal_grid_style.height,
            height: this.reveal_grid_style.height,
            text: ans_w,
            text_style: this.reveal_grid_style.text as IText,
            stroke: this.reveal_grid_style.stroke as IStroke,
          })
        )

        // 将当前的字母 添加进 已出现的字母列表
        this.arrival_letters.add(ans_w.toUpperCase())
        col_num++
      }
      row_num++
    }
  }
  draw_message = (y_offet: number) => {
    const index_location = this.reveal_letter_location
    // 渲染 Message 信息
    // 打入一个组，便于整体移动
    const [group_x, group_y] = leafer_position({
      x: 0,
      y: 0,
      width: this.words_grid_style.width,
      height: this.words_grid_style.height,
      white_border: this.white_border,
      stroke: this.words_grid_style.stroke as IStrokePaint,
      y_offet: y_offet,
      x_interval: this.words_grid_style.x_interval,
      y_interval: this.words_grid_style.y_interval,
    })

    // 创建两个组 便于管理
    const ans_group = new Group({
      x: group_x,
      y: group_y,
      editable: true,
      fill: '#FEB027',
      hitChildren: false,
    })
    const topic_group = new Group({
      x: group_x,
      y: group_y,
      fill: '#FEB027',
      hitChildren: false,
    })
    // 用来计算 words 的题目的方框长度，方便右侧的index的安装
    const words_box_len = (this.words_grid_style.width * this.max_wordlen) / 2

    // message 的字母的下标的数子
    let message_index_num = 1

    let col_num = 0
    for (const message of this.message_text.slice(0, 1)) {
      let row_num = 0
      for (const letter of message) {
        // 如果当前的 row_num 对 max_grid_num 取余 为0则换行
        if (row_num % this.max_grid_num == 0) {
          col_num += 1
          row_num = 0
        }
        if (letter == ' ') {
          row_num++
          continue
        }

        // 获取 mseeage 的位置
        const [real_x, real_y] = leafer_position({
          x: row_num,
          y: col_num,
          width: this.message_grid_style.width,
          height: this.message_grid_style.height,
          stroke: this.message_grid_style.stroke as IStrokePaint,
          white_border: 0,
          x_offet: 0,
          y_offet: 0,
          x_interval: this.words_grid_style.x_interval,
          y_interval: this.words_grid_style.y_interval,
        })
        // 渲染下标至信息拼图
        const [ans_msg_text, topic_msg_text] = this.draw_letter_index(
          index_location,
          real_x,
          real_y,
          message_index_num,
          this.words_grid_style.text?.font.font_size as number
        )
        ans_group.add(ans_msg_text)
        topic_group.add(topic_msg_text)

        // 随机获取 当前字母在 recode_letter_index 中的某个位置
        const random_posit = get_random_position_index(
          this.recode_letter_index,
          letter.toLowerCase()
        )

        // 判断 是否当前字母 是否在 以出现的列表中 或者 获取不到 对应的posit
        if (
          !this.arrival_letters.has(letter.toUpperCase()) ||
          compare_array(random_posit, [-1, -1])
        ) {
          topic_group.add(
            create_boxtext({
              x: real_x,
              y: real_y,
              width: this.message_grid_style.width,
              height: this.message_grid_style.height,
              text: letter,
              text_style: this.message_grid_style.text as IText,
              stroke: this.message_grid_style.stroke as IStroke,
            })
          )
        } else {
          topic_group.add(
            create_boxtext({
              x: real_x,
              y: real_y,
              width: this.message_grid_style.width,
              height: this.message_grid_style.height,
              text: '',
              text_style: this.message_grid_style.text as IText,
              stroke: this.message_grid_style.stroke as IStroke,
            })
          )
          // 将下标同样添加进 words 的 方框下
          const [random_x, random_y] = leafer_position({
            x: random_posit[1],
            y: random_posit[0],
            width: this.words_grid_style.width,
            height: this.words_grid_style.height,
            stroke: this.words_grid_style.stroke as IStrokePaint,
            white_border: this.white_border,
            x_offet: this.words_grid_style.width + words_box_len,
            y_offet: 0,
            x_interval: this.words_grid_style.x_interval,
            y_interval: this.words_grid_style.y_interval,
          })
          // 将 信息的提示下标添加 区域一拼图
          const [topic_scramble1_text, ans_scramble1_text] =
            this.draw_letter_index(
              index_location,
              random_x,
              random_y,
              message_index_num,
              this.words_grid_style.text?.font.font_size as number
            )
          this.ans_words_group.add(topic_scramble1_text)
          this.topic_words_group.add(ans_scramble1_text)
        }

        ans_group.add(
          create_boxtext({
            x: real_x,
            y: real_y,
            width: this.reveal_grid_style.height,
            height: this.reveal_grid_style.height,
            text: letter,
            text_style: this.reveal_grid_style.text as IText,
            stroke: this.reveal_grid_style.stroke as IStroke,
          })
        )
        message_index_num++
        row_num++
      }

      col_num += 0.2
    }
    // 获取 group 的长度 和位置 进行居中对齐
    const cx = x_center_position(
      ans_group.boxBounds.width,
      this.topic_frame.width as number
    )
    ans_group.x = cx
    topic_group.x = cx
    this.answer_frame.add(ans_group)
    this.topic_frame.add(topic_group)
  }

  /**
   * @description: 获取 words 中的每个单词中每个字母的下标位置
   * @param {string[]} words: 单词列表
   * @return {*}:
   */
  record_letter_positions = (words: string[]): LetterPositions => {
    const result: LetterPositions = {}

    words.forEach((word, wordIndex) => {
      Array.from(word).forEach((char, charIndex) => {
        const letter = char.toLowerCase()

        // 初始化字母记录数组（如果不存在）
        if (!result[letter]) {
          result[letter] = []
        }

        // 记录当前位置
        result[letter].push([wordIndex, charIndex])
      })
    })

    return result
  }

  /**
   * @description: 绘制在外面的下标
   * @param {*} real_x: 实际x
   * @param {*} real_y: 实际y
   * @param {*} index: 下标
   * @return {*}:
   */
  draw_letter_index = (
    index_location: 'inside' | 'outside',
    real_x: number,
    real_y: number,
    index: number,
    font_size: number = 100
  ): [Text, Text] => {
    if (index_location === 'inside') {
      const answer_text = new Text({
        x: real_x,
        y: real_y,
        offsetX: 20,
        fontSize: font_size,
        width: this.reveal_grid_style.width,
        textAlign: 'left',
        verticalAlign: 'top',
        fill: '#000000',
        text: `${index}`,
        editable: true,
      })

      const topic_text = answer_text.clone({}) as Text

      return [topic_text, answer_text]
    } else {
      let stroke_width = 10
      if (this.reveal_grid_style.stroke) {
        const ls_stroke = this.reveal_grid_style.stroke as IStrokePaint
        if (ls_stroke.style)
          stroke_width = ls_stroke.style.strokeWidth as number
      }
      real_y = real_y + this.reveal_grid_style.width + 100 - stroke_width - 100
      const answer_text = new Text({
        x: real_x,
        y: real_y,
        fontSize: font_size,
        width: this.reveal_grid_style.width,
        textAlign: 'center',
        verticalAlign: 'top',
        fill: '#000000',
        text: `${index}`,
        editable: true,
      })
      const topic_text = answer_text.clone({}) as Text
      return [topic_text, answer_text]
    }
  }
}
